#pragma once

#include "ReturnValueRecord.h"
#include "ExceptionRecord.h"
#include "ArgumentRecord.h"
#include "DefaultCompareFunction.h"
#include <string>

namespace MockItNow
{
	class FunctionCallRecord
	{
	public:
	
		FunctionCallRecord(const FunctionInfo* functionInfo, void* recordTarget, int numArguments);
		~FunctionCallRecord();

		template<typename T>
		void RecordArgument(T argument)
		{
			if (m_ignoreAllArguments == true)
				return;

			MOCKITNOW_ASSERT(m_recordIndex < m_numArgumentRecords);
			MOCKITNOW_ASSERT(m_argumentRecords[m_recordIndex] == 0);

			typedef ArgumentRecord<typename TypeInfo<T>::StorageType, typename TypeInfo<T>::CompareFunction> ArgumentRecordType;

			ArgumentRecordType* argumentRecord = new ArgumentRecordType(DefaultCompareFunction<T>().function);			
			argumentRecord->RecordArgument((const typename TypeInfo<T>::StorageType&)argument);

			m_argumentRecords[m_recordIndex] = argumentRecord;

			m_recordIndex++;
		}

		template<typename T>
		void ReplayArgument(T argument)
		{
			if (m_ignoreAllArguments == true)
				return;

			MOCKITNOW_ASSERT(m_replayIndex < m_numArgumentRecords);
			MOCKITNOW_ASSERT(m_argumentRecords[m_replayIndex] != 0);

			typedef ArgumentRecord<typename TypeInfo<T>::StorageType, typename TypeInfo<T>::CompareFunction> ArgumentRecordType;
			
			ArgumentRecordType* argumentRecord = dynamic_cast<ArgumentRecordType*>(m_argumentRecords[m_replayIndex]);

			if (argumentRecord == 0)
				ReportFailure("Type mismatch when replaying argument", m_argumentRecords[m_replayIndex]->GetTypeName(), GetFriendlyTypeName<T>());

			argumentRecord->ReplayArgument((const typename TypeInfo<T>::StorageType&)argument);

			m_replayIndex++;
		}

		template<typename T>
		void SetCompareFunction(int index, typename TypeInfo<T>::CompareFunction compareFunction)
		{
			MOCKITNOW_ASSERT(index < m_numArgumentRecords);
			MOCKITNOW_ASSERT(m_argumentRecords[index] != 0);
			MOCKITNOW_ASSERT(m_ignoreAllArguments == false);

			typedef ArgumentRecord<typename TypeInfo<T>::StorageType, typename TypeInfo<T>::CompareFunction> ArgumentRecordType;
			
			ArgumentRecordType* argumentRecord = dynamic_cast<ArgumentRecordType*>(m_argumentRecords[index]);

			if (argumentRecord == 0)
				ReportFailure("Type mismatch when setting compare function. ", m_argumentRecords[index]->GetTypeName(), GetFriendlyTypeName<T>());

			argumentRecord->SetCompareFunction(compareFunction);
		}

		template<typename T>
		void SetReturnValue(T returnValue)
		{
			if (m_returnValueRecord != 0)
				ReportFailure("Trying to set a return value twice! Did you forget to register a function?");

			typedef ReturnValueRecord<typename TypeInfo<T>::StorageType> ReturnValueRecordType;

			ReturnValueRecordType* returnValueRecord = new ReturnValueRecordType();
			returnValueRecord->SetReturnValue((typename TypeInfo<T>::StorageType)returnValue);

			m_returnValueRecord = returnValueRecord;
		}

		template<typename T>
		void SetArgumentReturnValue(int index, T returnValue)
		{
			MOCKITNOW_ASSERT(index < m_numArgumentRecords);

			if (m_argumentReturnRecords[index] != 0)
				ReportFailure("Trying to set an argument return value twice! Did you forget to register a function?");

			typedef ReturnValueRecord<typename TypeInfo<T>::StorageType> ReturnValueRecordType;

			ReturnValueRecordType* returnValueRecord = new ReturnValueRecordType();
			returnValueRecord->SetReturnValue((typename TypeInfo<T>::StorageType)returnValue);

			m_argumentReturnRecords[index] = returnValueRecord;
		}

		template<typename T>
		void SetException(T exceptionValue)
		{
			if (m_exceptionRecord != 0)
				ReportFailure("Trying to set an exception value twice! Did you forget to register a function?");

			typedef ExceptionRecord<typename TypeInfo<T>::StorageType> ExceptionRecordType;

			ExceptionRecordType* exceptionRecord = new ExceptionRecordType();
			exceptionRecord->SetException((typename TypeInfo<T>::StorageType)exceptionValue);

			m_exceptionRecord = exceptionRecord;
		}

		template<typename T>
		T GetReturnValue() const
		{
			typedef ReturnValueRecord<typename TypeInfo<T>::StorageType> ReturnValueRecordType;

			if (m_returnValueRecord == 0)
				ReportFailure("Missing return value. ", GetFriendlyTypeName<T>(), "<NOTHING>");

			ReturnValueRecordType* returnValueRecord = dynamic_cast<ReturnValueRecordType*>(m_returnValueRecord);

			if (returnValueRecord == 0)
				ReportFailure("Type mismatch when retrieving return value. ", GetFriendlyTypeName<T>(), m_returnValueRecord->GetTypeName());

			return returnValueRecord->GetReturnValue();
		}

		bool HasArgumentReturnValue(int index) const
		{
			if (m_ignoreAllArguments == true)
				return false;

			MOCKITNOW_ASSERT(index < m_numArgumentRecords);
			return m_argumentReturnRecords[index] != 0;
		}

		template<typename T>
		T GetArgumentReturnValue(int index) const
		{
			typedef ReturnValueRecord<typename TypeInfo<T>::StorageType> ReturnValueRecordType;

			MOCKITNOW_ASSERT(m_argumentReturnRecords[index] != 0);

			ReturnValueRecordType* returnValueRecord = dynamic_cast<ReturnValueRecordType*>(m_argumentReturnRecords[index]);

			if (returnValueRecord == 0)
				ReportFailure("Type mismatch when retrieving argument return value. ", GetFriendlyTypeName<T>(), m_argumentReturnRecords[index]->GetTypeName());

			return returnValueRecord->GetReturnValue();
		}

		void ThrowExceptionIfSet()
		{
			if (m_exceptionRecord == 0)
				return;

			m_exceptionRecord->ThrowValueAsException();
		}

		void IgnoreArgument(int index);
		void IgnoreArguments();
		void SetFailWhenCalled();
		void SetReturnAlways();
		void SetIgnoreTarget();

		void CallOriginal();

		void Verify();

		size_t GetFunctionAddress() const;
		void* GetRecordTarget() const;
		bool GetFailWhenCalled() const;
		bool GetReturnAlways() const;
		bool GetIgnoreTarget() const;
		bool GetCallOriginal() const;
		int GetNumArguments() const;
		bool HasReturnValue() const;
		const FunctionInfo* GetFunctionInfo() const;
		const std::string& GetReportedFailure() const;

		FunctionCallRecord* next;

	private:

		const FunctionInfo* m_functionInfo;
		void* m_recordTarget;

		bool m_failWhenCalled;
		bool m_returnAlways;
		bool m_ignoreAllArguments;
		bool m_ignoreTarget;

		int m_recordIndex;
		int m_replayIndex;

		ArgumentRecordInterface** m_argumentRecords;
		ReturnValueRecordInterface** m_argumentReturnRecords;
		int m_numArgumentRecords;

		ReturnValueRecordInterface* m_returnValueRecord;
		ExceptionRecordInterface* m_exceptionRecord;

		bool m_callOriginal;

		std::string m_reportedFailure;
	};
}
